﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LotteryVote.Entity;
using LotteryVote.Utility;
using LotteryVote.Entity.Bet;
using LotteryVote.Logic.Application;
using System.Web;
using LotteryVote.Logic.Limit;
using LotteryVote.Logic.Exceptions;

namespace LotteryVote.Logic.Bet
{
    internal class BetOrderBuilder
    {
        #region Properties
        private LimitChecker _checker;
        internal LimitChecker Checker
        {
            get
            {
                if (_checker == null)
                    _checker = new LimitChecker();
                return _checker;
            }
            set
            {
                _checker = value;
            }
        }

        private UserFacade _userFacade;
        public UserFacade BlUser
        {
            get
            {
                if (_userFacade == null)
                    _userFacade = new UserFacade();
                return _userFacade;
            }
            set
            {
                _userFacade = value;
            }
        }

        private UserCommissionFacade _userCommFacade;
        public UserCommissionFacade UserCommFacade
        {
            get
            {
                if (_userCommFacade == null)
                    _userCommFacade = new UserCommissionFacade();
                return _userCommFacade;
            }
            set
            {
                _userCommFacade = value;
            }
        }

        private NumLenFacade _numLenFacade;
        public NumLenFacade NumLenFacade
        {
            get
            {
                if (_numLenFacade == null)
                    _numLenFacade = new NumLenFacade();
                return _numLenFacade;
            }
            set
            {
                _numLenFacade = value;
            }
        }

        private IDictionary<CompanyType, IList<CompanyTypeSupportNumLen>> _numLenSupportDic;
        public IDictionary<CompanyType, IList<CompanyTypeSupportNumLen>> NumLenSupportDic
        {
            get
            {
                if (_numLenSupportDic == null)
                    _numLenSupportDic = new Dictionary<CompanyType, IList<CompanyTypeSupportNumLen>>();
                return _numLenSupportDic;
            }
        }
        #endregion
        /// <summary>
        /// 全部注单的下注总金额
        /// </summary>
        public decimal ExceptTurnover { get; private set; }
        /// <summary>
        /// 实际接收投注的注单总金额.
        /// </summary>
        public decimal ActualTurnover { get; private set; }
        /// <summary>
        /// 创建注单
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="speice">The speice.</param>
        /// <param name="betList">The bet list.</param>
        /// <param name="isCon">是否快速下单.</param>
        /// <returns></returns>
        public IDictionary<BetSheet, IList<BetOrder>> BuildOrder(User user, LotterySpeciesEnum speice, IList<BetItem> betList, bool isCon = false)
        {
            ExceptTurnover = ActualTurnover = 0;

            var todayLotteryCompany = TodayLotteryCompany.Instance.GetTodayCompany();       //获取今日开奖公司
            //TODO:做缓存处理，减少DA操作
            var userCommModel = UserCommFacade.GetMemberCommission(user.UserId, (int)speice);  //获取用户的佣金信息表
            Dictionary<BetSheet, IList<BetOrder>> betSheetDic = new Dictionary<BetSheet, IList<BetOrder>>();    //注单字典
            foreach (var betItem in betList)
            {
                IList<long> fullArrangementNum = null;      //全排列号码
                BetSheet sheet = new BetSheet               //注单
                {
                    Num = betItem.Num,
                    UserId = user.UserId,
                    Status = BetStatus.Valid
                };
                betSheetDic.Add(sheet, new List<BetOrder>());
                foreach (var companyId in betItem.CompanyList)      //先遍历公司列表，同个号码支持多个公司
                {
                    var company = todayLotteryCompany.Find(it => it.CompanyId == companyId);        //找到对应的公司
                    var companyType = (CompanyType)company.CompanyType.Id;
                    foreach (var wager in betItem.WargerList)       //便利下注订单
                    {
                        AddOrderToDic(betSheetDic[sheet], companyType, company, userCommModel, betItem.Num, wager, user, fullArrangementNum, isCon);
                    }
                }
            }
            return betSheetDic;
        }
        public IDictionary<BetSheet, IList<BetOrder>> BuildAutoBetOrder(User user, LotterySpeciesEnum specie, int[] companys, IList<KeyValuePair<PlayWayEnum, decimal>> wagers, AutoBetType betType)
        {
            int[] nums;
            switch (betType)
            {
                case AutoBetType.TwelveZodiac: nums = LotterySystem.Current.TwelveZodiac; break;
                case AutoBetType.EvenEven: nums = LotterySystem.Current.EvenEven; break;
                case AutoBetType.EvenOdd: nums = LotterySystem.Current.EvenOdd; break;
                case AutoBetType.OddEven: nums = LotterySystem.Current.OddEven; break;
                case AutoBetType.OddOdd: nums = LotterySystem.Current.OddOdd; break;
                default: throw new InvalidDataException("不可到达,数据异常!");
            }
            return BuildAutoElectionCodeOrder(user, specie, companys, wagers, nums);
        }

        #region Private Methods
        /// <summary>
        /// 创建自动选码注单.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="specie">The specie.</param>
        /// <param name="companys">The companys.</param>
        /// <param name="wagers">The wagers.</param>
        /// <param name="nums">The nums.</param>
        /// <returns></returns>
        private IDictionary<BetSheet, IList<BetOrder>> BuildAutoElectionCodeOrder(User user, LotterySpeciesEnum specie, int[] companys, IList<KeyValuePair<PlayWayEnum, decimal>> wagers, int[] nums)
        {
            PlayWayEnum[] allowBetPlayWay = new[] { PlayWayEnum.Head, PlayWayEnum.Last, PlayWayEnum.HeadAndLast };
            List<KeyValuePair<PlayWayEnum, decimal>> wagerList = new List<KeyValuePair<PlayWayEnum, decimal>>();
            wagers.ForEach(it =>
            {
                if (!allowBetPlayWay.Contains(it.Key)) return;
                if (it.Key == PlayWayEnum.HeadAndLast)
                {
                    wagerList.Add(new KeyValuePair<PlayWayEnum, decimal>(PlayWayEnum.Head, it.Value));
                    wagerList.Add(new KeyValuePair<PlayWayEnum, decimal>(PlayWayEnum.Last, it.Value));
                }
                else
                    wagerList.Add(it);
            });
            List<BetItem> betList = new List<BetItem>();
            var wagerItems = new List<LotteryVote.Entity.Bet.BetItem.WagerItem>();
            wagerList.ForEach(it =>
            {
                var gpw = LotterySystem.Current.GamePlayWay.Find(x =>
                    x.GameType.GameId == (int)GameTypeEnum.TwoDigital && x.PlayWay.WayId == (int)it.Key);
                wagerItems.Add(new BetItem.WagerItem
                {
                    GamePlayTypeId = gpw.Id,
                    IsFullPermutation = false,
                    Wager = it.Value
                });
            });
            nums.ForEach(num =>
            {
                betList.Add(new BetItem
                {
                    Num = num.ToString("D2"),
                    CompanyList = companys,
                    WargerList = wagerItems
                });
            });
            return BuildOrder(user, specie, betList, LotterySystem.Current.AutoElectionCodeCommission > 0);
        }
        /// <summary>
        /// 是否在投注时间内.
        /// </summary>
        /// <param name="company">The company.</param>
        /// <param name="gpw">The GPW.</param>
        /// <returns>
        ///   <c>true</c> if [is in bet time] [the specified company]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsInBetTime(LotteryCompany company, GamePlayWay gpw)
        {
            DateTime openTime = DateTime.Today.Add(company.OpenTime);
            DateTime closeTime = DateTime.Today.Add(company.CloseTime);
            PlayWayEnum orderPlayWay = (PlayWayEnum)gpw.PlayWay.WayId;
            if (orderPlayWay == PlayWayEnum.Last)
                closeTime = closeTime.AddMinutes(LotterySystem.Current.AdditionalLastBetMinutes);
            DateTime now = DateTime.Now;

            return now >= openTime && now <= closeTime;
        }

        private void AddOrderToDic(IList<BetOrder> orderList, CompanyType companyType, LotteryCompany company, CommissionGroupModel commModel,
            string num, LotteryVote.Entity.Bet.BetItem.WagerItem wager, User user, IList<long> fullArrangementNum, bool isCon = false)
        {
            List<GamePlayWay> gamePlayWayList = new List<GamePlayWay>();
            var gamePlayWay = LotterySystem.Current.GamePlayWay.Find(it => it.Id == wager.GamePlayTypeId);     //找到下注的游戏玩法
            var playWay = (PlayWayEnum)gamePlayWay.PlayWay.WayId;
            if (playWay == PlayWayEnum.HeadAndLast)     //头尾则分解为头，尾两注
            {
                Func<PlayWayEnum, GamePlayWay> findPlayWayFun = (pw) =>
                {
                    return LotterySystem.Current.GamePlayWay.Find(it => it.GameType.ValueEquals(gamePlayWay.GameType) && it.PlayWay.WayId == (int)pw);
                };
                var headPlayWay = findPlayWayFun(PlayWayEnum.Head);
                var lastPlayWay = findPlayWayFun(PlayWayEnum.Last);
                gamePlayWayList.Add(headPlayWay);
                gamePlayWayList.Add(lastPlayWay);
            }
            else
                gamePlayWayList.Add(gamePlayWay);
            Action<string, int, GamePlayWay> buildOrderToListFunc = (n, q, gpw) =>
            {
                var order = BuildBetOrder(n, q, commModel, gpw, company, wager.Wager, user, isCon);
                ExceptTurnover += order.Turnover;       //累加全部注单金额
                if (Checker.Check(order, user) && IsInBetTime(company, gpw))
                {
                    orderList.Add(order);
                    ActualTurnover += order.Turnover;       //累加实际可下注注单金额
                }
            };
            gamePlayWayList.ForEach(x =>
            {
                var counter = NumQuantityCounterFactory.GetFactory.GetCounter(x.GameType, GetNumLenSupportInDic(companyType));
                var betNum = counter.GetRealyBetNum(num);
                int numQuantity = CountNumQuantity(betNum, x, companyType); //计算该玩法的号码长度
                if (wager.IsFullPermutation)    //是否全排列下注
                {
                    if (fullArrangementNum == null) //获取号码全排列
                        fullArrangementNum = MathHelper.GetFullArrangement(int.Parse(betNum));
                    fullArrangementNum.ForEach(it => buildOrderToListFunc(it.ToString("D" + betNum.Length), numQuantity, x));
                }
                else
                    buildOrderToListFunc(betNum, numQuantity, x);
            });
        }

        /// <summary>
        /// 计算号码数量（不同玩法中号码所占数量，如2D HN公司4个，18A1个，3D又不同）.
        /// </summary>
        /// <param name="num">The num.</param>
        /// <param name="gamePlayWay">The game play way.</param>
        /// <param name="companyType">Type of the company.</param>
        /// <param name="isFullPermutation">if set to <c>true</c> [is full permutation].</param>
        /// <returns></returns>
        private int CountNumQuantity(string num, GamePlayWay gamePlayWay, CompanyType companyType)
        {
            var counter = NumQuantityCounterFactory.GetFactory.GetCounter(gamePlayWay.GameType, GetNumLenSupportInDic(companyType));
            return counter.CountNumQuantity(num, gamePlayWay.PlayWay);
        }
        /// <summary>
        /// 获取公司类型支持的号码长度表.
        /// </summary>
        /// <param name="companyType">Type of the company.</param>
        /// <returns></returns>
        private IList<CompanyTypeSupportNumLen> GetNumLenSupportInDic(CompanyType companyType)
        {
            if (!NumLenSupportDic.ContainsKey(companyType))
            {
                var numLenSupport = NumLenFacade.GetNumLengthByCompanyType((int)companyType);
                NumLenSupportDic.Add(companyType, numLenSupport);
            }
            return NumLenSupportDic[companyType] as IList<CompanyTypeSupportNumLen>;
        }
        /// <summary>
        /// 创建下注订单.
        /// </summary>
        /// <param name="num">The num.</param>
        /// <param name="numQuantity">The num quantity.</param>
        /// <param name="commModel">The comm model.</param>
        /// <param name="gamePlayWay">The game play way.</param>
        /// <param name="company">The company.</param>
        /// <param name="wager">The wager.</param>
        /// <returns></returns>
        private BetOrder BuildBetOrder(string num, int numQuantity, CommissionGroupModel commModel, GamePlayWay gamePlayWay,
            LotteryCompany company, decimal wager, User user, bool isCon = false)
        {
            var commValue = commModel.ConcreteCommissionList.Find(it => it.CompanyType.ValueEquals(company.CompanyType) && it.GameType.ValueEquals(gamePlayWay.GameType));
            double percentComm = isCon ? commValue.Commission - LotterySystem.Current.AutoElectionCodeCommission : commValue.Commission;
            decimal comm = (decimal)MathHelper.PercentageToDecimal(percentComm, 4);
            decimal turnOver = wager * numQuantity;
            decimal commission = comm * turnOver;
            BetOrder order = new BetOrder
            {
                Num = num,
                GamePlayWayId = gamePlayWay.Id,
                CompanyId = company.CompanyId,
                Amount = wager,
                Odds = commValue.Odds,
                Commission = commission,
                Turnover = turnOver,
                Net = 100 - percentComm,
                NetAmount = turnOver - commission,
                Status = BetStatus.Valid,
                UserId = user.UserId,
                AncestorCommission = BuildAncestorCommission(turnOver, user, commValue, commModel.CommGroup.SpecieId)
            };
            if (isCon)
            {
                order.DropWater += LotterySystem.Current.AutoElectionCodeCommission;
            }
            return order;
        }
        private IList<OrderAncestorCommInfo> BuildAncestorCommission(decimal turnOver, User user, ConcreteCommission orderConcreteComm, int specieId)
        {
            List<OrderAncestorCommInfo> ancestorCommList = new List<OrderAncestorCommInfo>();
            foreach (int roleId in Enum.GetValues(typeof(SystemRole)))
            {
                SystemRole role = (SystemRole)roleId;
                if (role == SystemRole.Guest) continue;
                var concreteComm = GetCommissionModel(user, role, specieId).ConcreteCommissionList
                .Find(it => it.GameType.ValueEquals(orderConcreteComm.GameType) && it.CompanyType.ValueEquals(orderConcreteComm.CompanyType));
                ancestorCommList.Add(new OrderAncestorCommInfo
                {
                    RoleId = roleId,
                    Commission = concreteComm.Commission,
                    CommAmount = turnOver * (decimal)(concreteComm.Commission / 100)
                });
            }
            return ancestorCommList;
        }
        private CommissionGroupModel GetCommissionModel(User user, SystemRole role, int specieId)
        {
            string key = string.Format("{0}_{1}", specieId, role.ToString());
            var commModel = HttpContext.Current.Session[key] as CommissionGroupModel;
            if (commModel == null)
            {
                commModel = UserCommFacade.GetUserCommission(BlUser.GetParentUser(user).Find(it => it.Role == role), user, specieId);
                HttpContext.Current.Session[key] = commModel;
            }
            return commModel;
        }
        #endregion
    }
}
